package com.ztr.bigdecima;

import java.math.BigDecimal;

/**
 * 用于高精确处理常用的数学运算
 */
public class ArithmeticUtils {
    //默认除法运算精度
    private static final int DEF_DIV_SCALE = 10;

    /**
     * 提供精确的加法运算。此方法使用BigDecimal类来避免浮点数加法时的精度问题。
     *
     * @param v1 被加数，类型为double。
     * @param v2 加数，类型为double。
     * @return 两个参数的和，类型为double。返回值经过BigDecimal的加法运算以确保精度。
     */
    public static double add(double v1, double v2) {
        // 将v1和v2转换为BigDecimal类型，以确保加法运算的精度
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        // 执行加法运算，并将结果转换回double类型
        return b1.add(b2).doubleValue();
    }


    /**
     * 提供精确的加法运算。此方法用于将两个字符串表示的数字相加，避免因为Java基本类型加法导致的精度丢失问题。
     *
     * @param v1 被加数，以字符串形式传入。
     * @param v2 加数，以字符串形式传入。
     * @return 两个参数的和，以BigDecimal类型返回，确保计算精度。
     */
    public static BigDecimal add(String v1, String v2) {
        // 将字符串转换为BigDecimal类型，以准备进行精确的加法运算
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        // 执行加法运算并返回结果
        return b1.add(b2);
    }


    /**
     * 提供精确的加法运算。
     * 该方法使用BigDecimal类来保证加法运算的精度，可以用于处理需要高精度计算的场景，例如金融计算中。
     *
     * @param v1    被加数，以字符串形式输入，可以是任意大小的数字。
     * @param v2    加数，以字符串形式输入，可以是任意大小的数字。
     * @param scale 保留的小数位数，用于控制结果的精度。如果需要整数相加，可以设置为0。
     * @return 两个参数的和，以字符串形式返回，确保了计算过程中的精度不会丢失。
     * @throws IllegalArgumentException 如果scale小于0，抛出此异常。
     */
    public static String add(String v1, String v2, int scale) {
        // 校验scale是否为非负整数
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        // 将字符串转换为BigDecimal类型，进行精确的数学运算
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        // 执行加法运算，并设置保留的小数位数，采用四舍五入的规则
        return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }


    /**
     * 提供精确的减法运算
     * 此方法用于执行双精度浮点数的减法操作，以避免由于浮点数精度问题导致的误差。
     *
     * @param v1 被减数
     * @param v2 减数
     * @return 两个参数的差
     */
    public static double sub(double v1, double v2) {
        // 将双精度浮点数转换为BigDecimal类型以确保计算精度
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        // 执行减法运算并返回结果
        return b1.subtract(b2).doubleValue();
    }


    /**
     * 提供精确的减法运算。
     * 此方法用于两个字符串表示的数字之间的减法运算，返回一个 BigDecimal 类型的结果，
     * 以确保运算的精确性，避免由于浮点数精度问题导致的错误。
     *
     * @param v1 被减数，以字符串形式传入。
     * @param v2 减数，以字符串形式传入。
     * @return 两个参数的差，返回一个 BigDecimal 对象。
     */
    public static BigDecimal sub(String v1, String v2) {
        // 将字符串转换为 BigDecimal 类型，以进行精确的数学运算
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        // 执行减法运算
        return b1.subtract(b2);
    }


    /**
     * 提供精确的减法运算
     *
     * @param v1    被减数，以字符串形式输入
     * @param v2    减数，以字符串形式输入
     * @param scale 保留的小数位数，必须为非负整数
     * @return 两个参数的差，以字符串形式返回，保留指定的小数位
     * @throws IllegalArgumentException 如果scale小于0，则抛出此异常
     */
    public static String sub(String v1, String v2, int scale) {
        // 校验scale是否为非负整数
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1); // 将v1转换为BigDecimal类型
        BigDecimal b2 = new BigDecimal(v2); // 将v2转换为BigDecimal类型
        // 执行减法运算，并设置精确度，四舍五入
        return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }


    /**
     * 提供精确的乘法运算
     * 本方法使用BigDecimal类来避免浮点数计算带来的精度问题，确保乘法运算的精确性。
     *
     * @param v1 被乘数，类型为double
     * @param v2 乘数，类型为double
     * @return 两个参数的积，类型为double
     */
    public static double mul(double v1, double v2) {
        // 将v1和v2转换为BigDecimal类型，以确保计算过程中不会丢失精度
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        // 使用BigDecimal的multiply方法进行乘法运算，并将结果转换回double类型
        return b1.multiply(b2).doubleValue();
    }


    /**
     * 提供精确的乘法运算
     * 此方法用于执行两个字符串表示的数的乘法操作，避免了由于Java基本类型浮点数运算带来的精度问题。
     *
     * @param v1 被乘数，以字符串形式传入。
     * @param v2 乘数，以字符串形式传入。
     * @return 两个参数的积，以BigDecimal类型返回，确保了计算的精确性。
     */
    public static BigDecimal mul(String v1, String v2) {
        // 将字符串转换为BigDecimal类型，以进行精确计算
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        // 执行乘法操作
        return b1.multiply(b2);
    }


    /**
     * 提供精确的乘法运算。
     * 此方法使用BigDecimal来避免浮点数计算带来的精度问题，特别适用于需要进行高精度计算的场景。
     *
     * @param v1    被乘数，即第一个乘数。
     * @param v2    乘数，即第二个乘数。
     * @param scale 保留的小数位数。此参数指定了计算结果应保留的小数位数，用于四舍五入。
     * @return 两个参数的积，结果保留了指定的小数位数。
     */
    public static double mul(double v1, double v2, int scale) {
        // 将浮点数转换为BigDecimal类型，以确保计算过程的高精度
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        // 执行乘法操作，并对结果进行四舍五入，然后返回结果
        return round(b1.multiply(b2).doubleValue(), scale);
    }


    /**
     * 提供精确的乘法运算。
     * 此方法用于执行两个数的乘法操作，并且可以指定结果的小数位数。
     *
     * @param v1    被乘数，以字符串形式输入。
     * @param v2    乘数，以字符串形式输入。
     * @param scale 保留的小数位数，是一个正整数或零。
     *              如果为负值，会抛出IllegalArgumentException异常。
     * @return 两个参数的积，以字符串形式返回。
     * 结果会根据scale参数四舍五入。
     */
    public static String mul(String v1, String v2, int scale) {
        // 检查scale参数是否合法
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        // 将输入的字符串转换为BigDecimal类型，进行精确计算
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        // 执行乘法操作，并设置保留的小数位数，使用四舍五入的规则
        return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }


    /**
     * 提供（相对）精确的除法运算，当发生除不尽的情况时，精确到
     * 小数点以后10位，以后的数字四舍五入。
     *
     * @param v1 被除数
     * @param v2 除数
     * @return 两个参数的商，精确到小数点后10位并四舍五入
     */
    public static double div(double v1, double v2) {
        return div(v1, v2, DEF_DIV_SCALE);
    }


    /**
     * 提供（相对）精确的除法运算。当发生除不尽的情况时，由scale参数指
     * 定精度，以后的数字四舍五入
     *
     * @param v1    被除数
     * @param v2    除数
     * @param scale 表示需要精确到小数点以后几位。该参数必须为非负整数。
     * @return 两个参数的商。返回值为double类型，精确到指定的小数位。
     * @throws IllegalArgumentException 如果scale小于0，抛出此异常。
     */
    public static double div(double v1, double v2, int scale) {
        // 检查scale参数是否合法
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        // 将被除数和除数转换为BigDecimal类型，以进行精确计算
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        // 执行除法运算，精确到指定的小数位，并处理除不尽的情况
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }


    /**
     * 提供（相对）精确的除法运算。当发生除不尽的情况时，由scale参数指
     * 定精度，以后的数字四舍五入
     *
     * @param v1    被除数，以字符串形式输入
     * @param v2    除数，以字符串形式输入
     * @param scale 表示需要精确到小数点以后几位，是一个正整数或零
     * @return 两个参数的商，以字符串形式返回
     * @throws IllegalArgumentException 如果scale小于0，则抛出此异常
     */
    public static String div(String v1, String v2, int scale) {
        if (scale < 0) { // 检查scale是否小于0，如果是，则抛出异常
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1); // 将被除数转换为BigDecimal类型
        BigDecimal b2 = new BigDecimal(v2); // 将除数转换为BigDecimal类型
        // 执行除法运算，精确到指定的小数位，并对结果进行四舍五入
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString();
    }


    /**
     * 提供精确的小数位四舍五入处理
     *
     * @param v     需要四舍五入的数字
     * @param scale 小数点后保留几位
     * @return 四舍五入后的结果
     * @throws IllegalArgumentException 如果scale小于0，抛出此异常
     */
    public static double round(double v, int scale) {
        // 检查scale是否为非正数
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(Double.toString(v)); // 将double转换为BigDecimal，以确保精确计算
        // 设置小数点位置并按照指定规则四舍五入
        return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }


    /**
     * 提供精确的小数位四舍五入处理
     *
     * @param v     需要四舍五入的数字。该参数以字符串形式传入，以便支持任意大小的数字，避免精度丢失。
     * @param scale 小数点后保留几位。该参数指定了结果应该保留的小数位数。如果为负，则抛出异常。
     * @return 四舍五入后的结果。返回值为字符串形式，以确保精确度不被丢失。
     * @throws IllegalArgumentException 如果scale小于0，抛出此异常。
     */
    public static String round(String v, int scale) {
        // 检查scale是否为非正数
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(v); // 将输入的字符串转换为BigDecimal类型，以处理大数和精确小数
        // 设置小数位数并执行四舍五入操作，返回结果的字符串表示
        return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }


    /**
     * 取余数
     * <p>
     * 该方法使用BigDecimal类来执行两个字符串表示的数字之间的除法操作，并返回除法的余数。
     * 通过指定的精度scale，可以控制返回结果的小数点后保留的位数。
     *
     * @param v1    被除数，以字符串形式输入
     * @param v2    除数，以字符串形式输入
     * @param scale 小数点后保留的位数，是一个整数
     * @return 余数，以字符串形式返回
     * @throws IllegalArgumentException 如果scale小于0，则抛出此异常
     */
    public static String remainder(String v1, String v2, int scale) {
        // 校验scale是否为非负整数
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        // 将字符串转换为BigDecimal类型，进行高精度计算
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        // 计算余数并设置保留位数，采用四舍五入的规则
        return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }


    /**
     * 取余数操作，使用BigDecimal类型进行高精度计算，避免浮点数误差。
     *
     * @param v1    被除数，BigDecimal类型。
     * @param v2    除数，BigDecimal类型。
     * @param scale 计算结果的小数点后保留位数。
     * @return 余数，BigDecimal类型，保留指定的位数。
     * @throws IllegalArgumentException 当scale小于0时抛出，确保位数合法。
     */
    public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) {
        // 校验scale是否合法
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        // 执行取余数操作，并设置保留位数
        return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP);
    }


    /**
     * 比较两个字符串表示的数值大小
     *
     * @param v1 被比较的数值字符串
     * @param v2 用于比较的数值字符串
     * @return 如果v1表示的数值大于v2表示的数值，则返回true；否则返回false。
     */
    public static boolean compare(String v1, String v2) {
        // 使用BigDecimal类来确保精确的浮点数比较
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);

        // 比较两个BigDecimal对象的大小
        int bj = b1.compareTo(b2);

        // 根据比较结果确定返回值
        boolean res;
        if (bj > 0)
            res = true;
        else
            res = false;

        return res;
    }

}
